package org.hackystat.ui.projectviewer.client;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import org.hackystat.ui.projectviewer.client.common.ProjectParcel;

import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.WindowResizeListener;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ChangeListener;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * This class embodies the edit project panel.
 * @author David J. Nickles
 *
 */
public class ProjectEditPanel extends Composite implements ClickListener, WindowResizeListener, 
  ChangeListener {

  private Session session;
  private ProjectParcel parcel;
  private Map propertiesMap;
  private Button newButton;
  private Button saveButton;
  private Button cancelButton;
  private Label nameLabel;
  private TextBox nameBox;
  private Label ownerLabel;
  private Label ownerBox;
  private Label descLabel;
  private TextArea descArea;
  private Label startTime;
  private TextBox startBox;
  private Label endTime;
  private TextBox endBox;
  private Label invitationLabel;
  private TextBox invitationBox;
  private Button addInvitationButton;
  private Button delInvitationButton;
  private ListBox invitationList;
  private Label membersLabel;
  private Button delMemberButton;
  private ListBox membersList;
  private Label propertiesLabel;
  private TextBox propKeyBox;
  private TextBox propValueBox;
  private Button addPropButton;
  private Button delPropButton;
  private ListBox propertiesList;
  private Label uriLabel;
  private TextBox uriBox;
  private ListBox uriList;
  private Button addUriButton;
  private Button delUriButton;
  private Label panelTitle;
  private Grid grid;
  private HorizontalPanel titleRow;

  /**
   * Construct a new Owner Edit Project Panel.
   * @param session the current session.
   * @param parcel the project Parcel to edit or null if creating a new Project.
   */
  public ProjectEditPanel(Session session, ProjectParcel parcel) {
    this.session = session;
    boolean editMode = false;
    if (parcel == null) {
      this.parcel = new ProjectParcel();
      this.propertiesMap = new HashMap();
      editMode = false;
      this.panelTitle = new Label("Add a New Project:");
    }
    else {
      this.parcel = parcel;
      this.propertiesMap = this.parcel.getProperties();
      editMode = true;
      this.panelTitle = new Label("Edit Project:");
    }
    
    VerticalPanel vp = new VerticalPanel();
    vp.setStyleName(Styles.PANELSTYLE);
    
    titleRow = new HorizontalPanel();
    titleRow.setWidth(Integer.toString(Window.getClientWidth() - 42) + Styles.PIXELSUFFIX);
    titleRow.add(this.panelTitle);

    HorizontalPanel titleCommands = new HorizontalPanel();
    this.newButton = new Button("Save New");
    this.saveButton = new Button("Save Edit");
    if (editMode) {
      titleCommands.add(this.saveButton);
      this.saveButton.addClickListener(this);
    }
    else {
      titleCommands.add(this.newButton);
      this.newButton.addClickListener(this);
    }
    titleCommands.add(this.cancelButton = new Button("Cancel"));
    this.cancelButton.addClickListener(this);
    titleRow.add(titleCommands);
    titleRow.setCellHorizontalAlignment(titleCommands, HasHorizontalAlignment.ALIGN_RIGHT);
    vp.add(titleRow);
    
    this.grid = new Grid(9, 2);
    
    this.grid.setWidget(0, 0, this.nameLabel = new Label("Project Name:"));
    this.nameLabel.setStyleName(Styles.ENTRYLABELSTYLE);
    this.grid.setWidget(0, 1, this.nameBox = new TextBox());
    this.nameBox.setTitle("Enter a name for this project.");

    this.grid.setWidget(1, 0, this.ownerLabel = new Label("Owner:"));
    this.ownerLabel.setStyleName(Styles.ENTRYLABELSTYLE);
    this.grid.setWidget(1, 1, this.ownerBox = new Label(this.session.getUser()));
    this.ownerBox.setStyleName(Styles.ENTRYLABELSTYLE);
    
    this.grid.setWidget(2, 0, this.descLabel = new Label("Description:"));
    this.descLabel.setStyleName(Styles.ENTRYLABELSTYLE);
    this.grid.setWidget(2, 1, this.descArea = new TextArea());
    this.descArea.setTitle("Enter a description for the project.");
    this.descArea.setWidth("400px");
    this.descArea.setHeight("50px");
    
    this.grid.setWidget(3, 0, this.startTime = new Label("Start Date:"));
    this.startTime.setStyleName(Styles.ENTRYLABELSTYLE);
    VerticalPanel row4col2 = new VerticalPanel();
    row4col2.add(this.startBox = new TextBox());
    this.startBox.setTitle("Enter the first date of the project term.");
    this.startBox.addChangeListener(this);
    Label startDateExample = new Label("Format: YYYY-MM-DD");
    startDateExample.setStyleName(Styles.EXAMPLELABELSTYLE);
    row4col2.add(startDateExample);
    this.grid.setWidget(3, 1, row4col2);

    this.grid.setWidget(4, 0, this.endTime = new Label("End Date:"));
    this.endTime.setStyleName(Styles.ENTRYLABELSTYLE);
    VerticalPanel row4col4 = new VerticalPanel();
    row4col4.add(this.endBox = new TextBox());
    this.endBox.addChangeListener(this);
    this.endBox.setTitle("Enter the last date of the project term.");
    Label endDateExample = new Label("Format: YYYY-MM-DD");
    endDateExample.setStyleName(Styles.EXAMPLELABELSTYLE);
    row4col4.add(endDateExample);
    this.grid.setWidget(4, 1, row4col4);
    
    this.membersList = new ListBox();
    if (editMode) {
      this.grid.setWidget(5, 0, this.membersLabel = new Label("Members:"));
      this.membersLabel.setStyleName(Styles.ENTRYLABELSTYLE);
      HorizontalPanel hp5 = new HorizontalPanel();
      hp5.add(this.membersList);
      this.membersList.setWidth("200px");
      this.membersList.setTitle("Select a member and click Remove to delete that member " + 
        "and all that member's data from the project.");
      hp5.add(this.delMemberButton = new Button("Remove"));
      this.delMemberButton.addClickListener(this);
      this.delMemberButton.setTitle(
        "Deletes the currently selected member and all that member's data from the project.");
      this.grid.setWidget(5, 1, hp5);
    }
    
    this.grid.setWidget(6, 0, this.invitationLabel = new Label("Invitations:"));
    this.invitationLabel.setStyleName(Styles.ENTRYLABELSTYLE);
    Grid grid6 = new Grid(1,4);
    grid6.setWidget(0, 0, this.invitationBox = new TextBox());
    this.invitationBox.setTitle(
      "Type a registered user's name and click Invite to create an invitation.");
    grid6.setWidget(0, 1, this.addInvitationButton = new Button("Invite"));
    this.addInvitationButton.setTitle("Invites the entered user to this project.");
    this.addInvitationButton.addClickListener(this);
    grid6.setWidget(0, 2, this.invitationList = new ListBox());
    this.invitationList.setTitle("Select a user and click Remove to delete the invitation.");
    this.invitationList.setWidth("200px");
    grid6.setWidget(0, 3, this.delInvitationButton = new Button("Remove"));
    this.delInvitationButton.setTitle("Deletes the selected invitation from the project.");
    this.delInvitationButton.addClickListener(this);
    this.grid.setWidget(6, 1, grid6);
    
    this.grid.setWidget(7, 0, this.propertiesLabel = new Label("Properties:"));
    this.propertiesLabel.setStyleName(Styles.ENTRYLABELSTYLE);
    Grid grid7 = new Grid(2,5);
    Label keyLabel = new Label("Key:");
    keyLabel.setStyleName(Styles.ENTRYLABELSTYLE);
    grid7.setWidget(0, 0, keyLabel);
    grid7.setWidget(0, 1, this.propKeyBox = new TextBox());
    this.propKeyBox.setTitle(
      "Type both a Key & Value property and click Add to associate the property to this project.");
    Label valueLabel = new Label("Value:");
    valueLabel.setStyleName(Styles.ENTRYLABELSTYLE);
    grid7.setWidget(1, 0, valueLabel);
    grid7.setWidget(1, 1, this.propValueBox = new TextBox());
    this.propValueBox.setTitle(
      "Type both a Key & Value property and click Add to associate the property to this project.");
    grid7.setWidget(0, 2, this.addPropButton = new Button("Add"));
    this.addPropButton.setTitle("Adds the entered Key-Value property pair to this project.");
    this.addPropButton.addClickListener(this);
    grid7.setWidget(0, 3, this.propertiesList = new ListBox());
    this.propertiesList.setWidth("200px");
    this.propertiesList.setTitle("Select a property and click Remove to delete it.");
    grid7.setWidget(0, 4, this.delPropButton = new Button("Remove"));
    this.delPropButton.setTitle("Deletes the currently selected property from the project.");
    this.delPropButton.addClickListener(this);
    this.grid.setWidget(7, 1, grid7);
    
    this.grid.setWidget(8, 0, this.uriLabel = new Label("URIs:"));
    this.uriLabel.setTitle("This project's associated Univerisal Resource Indicators.");
    this.uriLabel.setStyleName(Styles.ENTRYLABELSTYLE);
    Grid grid8 = new Grid(1,5);
    grid8.setWidget(0, 0, this.uriBox = new TextBox());
    this.uriBox.setTitle(
      "Type a URI into this field and click Add to associate it with the project.");
    grid8.setWidget(0, 1, this.addUriButton = new Button("Add"));
    this.addUriButton.setTitle("Click Add to associate the entered URI with the project.");
    this.addUriButton.addClickListener(this);
    grid8.setWidget(0, 2, this.uriList = new ListBox());
    this.uriList.setWidth("200px");
    this.uriList.setTitle("Select a URI and click Remove to delete it.");
    grid8.setWidget(0, 3, this.delUriButton = new Button("Remove"));
    this.delUriButton.setTitle("Deletes the currently selected URI from the project.");
    this.delUriButton.addClickListener(this);
    this.grid.setWidget(8, 1, grid8);
    
    vp.add(this.grid);
    
    if (editMode) {
      this.nameBox.setText(this.parcel.getName());
      this.descArea.setText(this.parcel.getDescription());
      this.startBox.setText(this.parcel.getStartTime());
      this.endBox.setText(this.parcel.getEndTime());
      Iterator memberIt = this.parcel.getMembers().iterator();
      while (memberIt.hasNext()) {
        this.membersList.addItem((String)memberIt.next());
      }
      Iterator inviteIt = this.parcel.getInvitations().iterator();
      while (inviteIt.hasNext()) {
        this.invitationList.addItem((String)inviteIt.next());
      }
      Iterator propIt = this.parcel.getProperties().entrySet().iterator();
      while (propIt.hasNext()) {
        Entry entry = (Entry)propIt.next();
        this.propertiesList.addItem((String)entry.getKey() + ':' + (String)entry.getValue(), 
          (String)entry.getKey());
      }
      Iterator uriIt = this.parcel.getUriPatterns().iterator();
      while (uriIt.hasNext()) {
        this.uriList.addItem((String)uriIt.next());
      }
    }
    initWidget(vp);
    Window.addWindowResizeListener(this);
  }
  
  /**
   * This method sets the state of the user & key validation.
   * @param projects the list of projects for this user.
   * 
   * @gwt.typeArgs projects <java.util.ArrayList<java.lang.String>>
   */
  private void setUserProjects(ArrayList projects) {
    this.session.setProjects(projects);
    RootPanel.get(CompositeWidget.PROJECTVIEWER_ELEMENT).add(
        new ProjectListPanel(session));
    removeFromParent();
  }
  
  /**
   * Check with the servlet to see if the user/password is valid.
   */
  private void rpcGetProjectParcels() {
    RPCServiceFactory.getInstance().getService().getProjectParcels(
      this.session.getSensorbaseHost(), this.session.getUser(), this.session.getPassword(), 
      new AsyncCallback()
      {
        public void onSuccess(Object result) {
          setUserProjects((ArrayList) result);
        }

        public void onFailure(Throwable caught) {
          if (caught instanceof com.google.gwt.user.client.rpc.InvocationException) {
            CompositeWidget.log(RPCServiceFactory.PRCERRORNAME, 
              "Cannot invoke user validation on servlet.");
          } 
          else {
            CompositeWidget.log(RPCServiceFactory.PRCERRORNAME, caught.toString());
          }
        }
      });
  }  

  /** 
   * Fill parcel and then invoke save.
   */
  public void prepareParcel() {
    this.parcel = new ProjectParcel();
    this.parcel.setName(this.nameBox.getText());
    this.parcel.setOwner(this.session.getUser());
    this.parcel.setDescription(this.descArea.getText());
    this.parcel.setStartTime(this.startBox.getText());
    this.parcel.setEndTime(this.endBox.getText());
    
    /** @gwt.typeArgs <java.lang.String> */
    ArrayList members = new ArrayList();
    for (int i = 0; i < this.membersList.getItemCount(); i++) {
      members.add(this.membersList.getItemText(i));
    }
    this.parcel.setMembers(members);

    /** @gwt.typeArgs <java.lang.String> */
    ArrayList invites = new ArrayList();
    for (int i = 0; i < this.invitationList.getItemCount(); i++) {
      invites.add(this.invitationList.getItemText(i));
    }
    this.parcel.setInvitations(invites);

    /** @gwt.typeArgs <java.lang.String> */
    ArrayList patterns = new ArrayList();
    for (int i = 0; i < this.uriList.getItemCount(); i++) {
      patterns.add(this.uriList.getItemText(i));
    }
    this.parcel.setUriPatterns(patterns);
    
    this.parcel.setProperties(this.propertiesMap);
  }
  
  /**
   * This method sets the state of the host uri validation.
   * @param state String represents the state of the user valid=true, invalid=false
   */
  public void processNewProjectResult(String state) {
    if (Boolean.TRUE.toString().equals(state)) {
      RootPanel.get(CompositeWidget.PROJECTEDIT_ELEMENT).clear();
      RootPanel.get(CompositeWidget.PROJECTVIEWER_ELEMENT).clear();
      rpcGetProjectParcels();
    }
    else {
      CompositeWidget.log("Error", state);
    }
  }
  
  /**
   * Invoke NewProject on the servlet.
   */
  public void rpcPutProject () {
    RPCServiceFactory.getInstance().getService().putProject(session.getSensorbaseHost(), 
      session.getUser(), session.getPassword(), this.parcel, new AsyncCallback()
      {
        public void onSuccess(Object result) {
          processNewProjectResult((String) result);
        }

        public void onFailure(Throwable caught) {
          if (caught instanceof com.google.gwt.user.client.rpc.InvocationException) {
            CompositeWidget.log(RPCServiceFactory.PRCERRORNAME, 
              "Cannot invoke user validation on servlet.");
          } 
          else {
            CompositeWidget.log(RPCServiceFactory.PRCERRORNAME, caught.toString());
          }
        }
      });
  }  

  /** 
   * Listens for clicks on the UI elements.
   * @param sender the UI widget that is losing focus.
   */
  public void onClick(Widget sender) {
    if (sender.equals(this.cancelButton)) {
      RootPanel.get(CompositeWidget.PROJECTEDIT_ELEMENT).clear();
      RootPanel.get(CompositeWidget.PROJECTVIEWER_ELEMENT).clear();
      rpcGetProjectParcels();
    }
    else if (sender.equals(this.newButton)) {
      prepareParcel();
      rpcPutProject();
    }
    else if (sender.equals(this.saveButton)) {
      prepareParcel();
      rpcPutProject();
    }
    else if (sender.equals(this.delMemberButton)) {
      String name = this.membersList.getValue(this.membersList.getSelectedIndex());
      this.parcel.getMembers().remove(name);
      this.membersList.removeItem(this.membersList.getSelectedIndex());
    }
    else if (sender.equals(this.addInvitationButton)) {
      String name = this.invitationBox.getText();
      if (name.length() > 0 && !name.equals(this.session.getUser()) 
        && !this.parcel.getInvitations().contains(name)) {
        if (this.parcel.getInvitations() == null) {
          Window.alert("Parcel contains null invitation list.");
        }
        else {
          this.parcel.getInvitations().add(name);
        }
        this.invitationList.addItem(name);
        this.invitationBox.setText("");
      }
    }
    else if (sender.equals(this.delInvitationButton)) {
      String name = this.invitationList.getValue(this.invitationList.getSelectedIndex());
      this.parcel.getInvitations().remove(name);
      this.invitationList.removeItem(this.invitationList.getSelectedIndex());
    }
    else if (sender.equals(this.addUriButton)) {
      String pattern = this.uriBox.getText();
      if (pattern.length() > 0) {
        this.parcel.getUriPatterns().add(pattern);
        this.uriList.addItem(pattern);
        this.uriBox.setText("");
      }
    }
    else if (sender.equals(this.delUriButton)) {
      String name = this.uriList.getValue(this.uriList.getSelectedIndex());
      this.parcel.getUriPatterns().remove(name);
      this.uriList.removeItem(this.uriList.getSelectedIndex());
    }
    else if (sender.equals(this.addPropButton)) {
      if (this.propKeyBox.getText().length() > 0 && this.propValueBox.getText().length() > 0
          && !this.propertiesMap.containsKey(this.propKeyBox.getText())) {
        this.propertiesMap.put(this.propKeyBox.getText(), this.propValueBox.getText());
        this.propertiesList.addItem(this.propKeyBox.getText() + ':' + this.propValueBox.getText(), 
          this.propKeyBox.getText());
        this.propKeyBox.setText("");
        this.propValueBox.setText("");
      }
    }
    else if (sender.equals(this.delPropButton) && (this.propertiesList.getSelectedIndex() > -1)) {
      String key = this.propertiesList.getValue(this.propertiesList.getSelectedIndex());
      this.propertiesMap.remove(key);
      this.propertiesList.removeItem(this.propertiesList.getSelectedIndex());
    }
  }

  /** 
   * Listens for user resizing of the window.
   * @param width of the adjusted window..
   * @param height of the adjusted window.
   */
  public void onWindowResized(int width, int height) {
    this.titleRow.setWidth(Integer.toString(Window.getClientWidth() - 42) + Styles.PIXELSUFFIX);
  }

  /** 
   * Listens for content changes in the UI elements.
   * @param sender the UI widget that has changed.
   */
  public void onChange(Widget sender) {
    if (sender.equals(this.startBox)) {
      try {
        if (this.startBox.getText().length() != 10) {
          throw new IllegalArgumentException();
        }
        DateTimeFactories.getInstance().getDatefactory().parse(this.startBox.getText());
      }
      catch (Exception iae) {
        this.startBox.setFocus(true);
        Window.alert("The date must be a valid date in the format YYYY-MM-DD.");
      }
      if (this.parcel.getEndTime() != null) {
        Date s = DateTimeFactories.getInstance().getDatefactory().parse(this.startBox.getText());
        Date e = DateTimeFactories.getInstance().getDatefactory().parse(this.endBox.getText());
        if (s.after(e)) {
          Window.alert("The project end date must occur after the entered start date.");
          this.startBox.setFocus(true);
        }
      }
    }
    else if (sender.equals(this.endBox)) {
      try {
        if (this.endBox.getText().length() != 10) {
          throw new IllegalArgumentException();
        }
        DateTimeFactories.getInstance().getDatefactory().parse(this.endBox.getText());
      }
      catch (Exception iae) {
        Window.alert("The date must be a valid date in the format YYYY-MM-DD.");
        this.endBox.setFocus(true);
      }
      if (this.parcel.getEndTime() != null) {
        Date s = DateTimeFactories.getInstance().getDatefactory().parse(this.startBox.getText());
        Date e = DateTimeFactories.getInstance().getDatefactory().parse(this.endBox.getText());
        if (s.after(e)) {
          Window.alert("The project end date must occur after the entered start date.");
        }
      }
    }
  }
}
